<template>
  <div class="bar-3d" :style="{width:width, height:height}">
    <canvas ref="bar3d" :width="width" :height="height" @mousemove="$$_handlerMousemove"></canvas>
    <div class="bar-3d--tip" ref="tip">
      <div class="bar-3d--tip__left">
        {{leftValue}}
        <div class="bar-3d--tip__left__down"></div>
      </div>
      <div class="bar-3d--tip__right">
        {{rightValue}}
        <div class="bar-3d--tip__right__down"></div>
      </div>
    </div>
  </div>
</template>

<script>
import canvasProps from "../../common/js/canvas-props";
const TipTriggerConstant = {
  hover: 'hover',  //鼠标移入触发
  show: 'show',     //直接显示
};
const TypeConstant = {
  cuboid: "cuboid",
  cylinder: "cylinder"
};
const DirectionConstant = {
  vertical: 'vertical',
  horizontal: 'horizontal'
}
export default {
  name: 'bar-3d',
  mixins: [ canvasProps ],
  data() {
    return {
      cvs: null,
      maxValue: 0,  //数据中的最大数据
      yAxisMaxValue: 0, //比如数据中的最大数据为568 则值为600  若最大数据为123 此值为200 就是大于它的最小整百、千、万的数
      w: parseInt(this.width),
      h: parseInt(this.height),
      legendHeight: 10,
      xPoint: [],  //坐标轴x上的分类点{x: ..., y: ...}
      leftValue: 0,
      rightValue: 0,
      xyScale: [],   //xy坐标范围，用于显示tip
      showTipIndex: -1
    }
  },
  watch: {
    series: {
      handler(v) {
        if (this.cvs) {
          this.cvs.clearRect(0, 0, this.w + 1, this.h + 1);
          this.$$_draw();
        }
      },
      deep: true
    }
  },
  computed: {
    isHorizontal() {
      return this.direction === DirectionConstant.horizontal
    }
  },
  created() {
    const mv = [];
    this.series.forEach(item => {
      mv.push(item.data.reduce((max, currentValue) => max < currentValue ? currentValue : max, 0));
    });
    this.maxValue = mv.reduce((max, v) => max < v ? v : max, 0);
  },
  mounted() {
    if (this.$refs.bar3d.getContext) {
      this.cvs = this.$refs.bar3d.getContext('2d');
      this.$$_draw();
    }
  },
  methods: {
    $$_handlerMousemove(e) {
      this.$refs.tip.style.display = "none";
      if (this.showBoxTip && this.series.length === 2) {
        const [x, y] = [e.offsetX, e.offsetY];
        // console.log(x, y, this.xyScale)
        for (let i = 0; i < this.xyScale.length; i++) {//console.log(x, y, this.xyScale, e)
          if (x >= this.xyScale[i][0] && x <= this.xyScale[i][1] && y >= this.xyScale[i][2] && y <= this.xyScale[i][3]) {
            this.showTipIndex = i;
            this.leftValue = this.series[0].data[i];
            this.rightValue = this.series[1].data[i];
            this.$refs.tip.style.left = `${(this.xyScale[i][0] + this.xyScale[i][1]) / 2 - 45}px`;
            this.$refs.tip.style.top = `${(this.xyScale[i][2] - 60)}px`;
            this.$refs.tip.style.display = "block";
            break;
          }
        }
      }
    },
    $$_draw() {
      if (this.isHorizontal) {
        this.$refs.bar3d.style.transform = "rotate(90deg)";
      }
      this.$$_drawX();
      this.$$_drawY();
      this.$$_drawData();
      if (this.showLegend) this.$$_drawLegend();
    },
    $$_drawLegend() {
      for (let i = 0, len = this.series.length; i < len; i++) {
        if (!this.series[i].name) return;
        const xy = {
          x: this.w - 60 - i * (45 + this.padding[1]),
          y: Math.sqrt(this.xy[1] ** 2 + this.xy[0] ** 2) + 2 * this.xy[1] + 5 + 1
        };
        let h = Math.sqrt(this.xy[1] ** 2 + this.xy[0] ** 2) + 5;
        if (this.type === TypeConstant.cuboid) this.$$_drawCuboid(xy, h, this.series[len - 1 - i].color);
        else {
          h = h - 8;
          this.$$_drawCylinder(xy, h, this.series[len - 1- i].color);
        }
        //写字
        this.cvs.fillStyle = this.series[len - 1 - i].color[0];
        //这里的字体大小不太准，需要准确控制，可以通过props传入
        this.cvs.font = `${parseInt(this.fontSize) - 3 <= 10 ? parseInt(this.fontSize) : parseInt(this.fontSize) - 3}px sans-serif`;
        this.cvs.textAlign = "left";
        this.cvs.textBaseline = "middle";
        this.cvs.fillText(this.series[len - 1 - i].name, xy.x + this.xy[0] + 5, xy.y - h / 2);
      }
    },
    $$_drawX() {
      //画线
      this.cvs.beginPath();
      this.cvs.strokeStyle = this.xAxis.color;
      this.cvs.moveTo(this.padding[1], this.h - this.padding[0]);
      this.cvs.lineTo(this.w, this.h - this.padding[0]);
      this.cvs.stroke();
      //写字
      const t = (this.w - this.padding[1]) / (this.xAxis.data.length + 1);
      this.cvs.fillStyle = this.xAxis.color;
      this.cvs.font = `${this.fontSize} sans-serif`;
      this.cvs.textAlign = "center";
      this.xAxis.data.forEach((v, i) => {
        const x = this.padding[1] + (i + 1) * t, y = this.h - this.padding[0] / 2;
        this.cvs.fillText(v, x - t/4, y);
        this.xPoint.push({x: x - t/4, y: this.h - this.padding[0]});
      })
    },
    $$_drawY() {
      //画线
      this.cvs.beginPath();
      this.cvs.strokeStyle = this.yAxis.color;
      if (this.isHorizontal) this.cvs.strokeStyle = "rgba(0, 0, 0, 0)"; //水平展示的时候隐藏y轴
      this.cvs.moveTo(this.padding[1], this.h - this.padding[0]);
      this.cvs.lineTo(this.padding[1], this.padding[0] + this.legendHeight);
      this.cvs.stroke();
      //写字
      this.cvs.fillStyle = this.yAxis.color;
      if (this.isHorizontal) this.cvs.fillStyle = "rgba(0, 0, 0, 0)"; //水平展示的时候隐藏y轴
      this.cvs.font = `${this.fontSize} sans-serif`;
      this.cvs.textAlign = "right";
      if (this.yAxis.data && this.yAxis.data.length > 0) {
        const t = (this.h - this.padding[0] * 2 - this.legendHeight) / this.yAxis.data.length;
        this.yAxis.data.forEach((v, i) => {
          this.cvs.fillText(v, this.padding[1] - 5, this.h - this.padding[0] - i * t)
        })
      } else {
        const v = `${this.maxValue}`, len = v.length;
        let maxValue = parseInt(v.charAt(0)) + 1;
        for (let i = 0; i < len - 1; i++) {
          maxValue *= 10;
        }
        this.yAxisMaxValue = maxValue;
        const t = (this.h - this.padding[0] * 2 - this.legendHeight) / 4;
        let per = maxValue / 4, temp = 0;
        for (let i = 0; i < 4; i++) {
          this.cvs.fillText(temp, this.padding[1] - 5, this.h - this.padding[0] - i * t);
          temp += per;
        }
      }
      //写单位
      if (this.yAxis.name) {
        this.cvs.fillText(this.yAxis.name, this.padding[1] - 5, this.padding[0] + this.legendHeight)
      }
    },
    $$_drawData() {
      for (let i = 0, len = this.series.length; i < len && i < 2; i++) {  //目前定死数组大小为1 或 2
        this.series[i].data.forEach((item, index) => {
          const xy = JSON.parse(JSON.stringify(this.xPoint[index]));
          if (len === 2) {
            if (i === 0) xy.x -= this.xy[0];
            else if (i === 1) xy.x += this.xy[0];
          }
          const h = (this.h - this.padding[0] * 2 - this.legendHeight) / this.yAxisMaxValue * item;//item / this.maxValue * (this.h - this.padding[0] * 2);  //比例兑换后的高度
          if (this.series.length === 2) {
            if (this.xyScale[index]) {    //组装范围点
              const ss = this.xyScale[index];
              ss[0] > xy.x - this.xy[0] && (ss[0] = xy.x - this.xy[0]);
              ss[1] < xy.x + this.xy[0] && (ss[1] = xy.x + this.xy[0]);
              ss[2] > xy.y - h && (ss[2] = xy.y - h);
              ss[3] < xy.y && (ss[3] = xy.y);
            } else this.xyScale[index] = [xy.x - this.xy[0], xy.x + this.xy[0], xy.y - h, xy.y];
          }
          if (this.type === TypeConstant.cuboid) this.$$_drawCuboid(xy, h, this.series[i].color);
          else this.$$_drawCylinder(xy, h, this.series[i].color);
          this.tipTrigger === TipTriggerConstant.show && this.$$_drawTopTipNumber(item, xy, h, this.series[i].color[0]);
        })
      }
    },
    //画长方体
    $$_drawCuboid(xy, height, color) {
      this.cvs.lineWidth = 0.5;
      //画深色面
      this.cvs.beginPath();
      this.cvs.fillStyle = color[0]; //"rgb(100, 100, 200)";
      this.cvs.moveTo(xy.x, xy.y);
      this.cvs.lineTo(xy.x - this.xy[0], xy.y - this.xy[1]);
      this.cvs.lineTo(xy.x - this.xy[0], xy.y - height);
      this.cvs.lineTo(xy.x, xy.y - height + this.xy[1]);
      this.cvs.fill();

      //画线 和 浅色面
      this.cvs.beginPath();
      this.cvs.strokeStyle = color[0]; //"rgba(100, 100, 200)";
      this.cvs.fillStyle = color[1]; //"rgba(100, 100, 200, .5)";
      this.cvs.moveTo(xy.x, xy.y);
      this.cvs.lineTo(xy.x + this.xy[0], xy.y - this.xy[1]);
      this.cvs.lineTo(xy.x + this.xy[0], xy.y - height);
      this.cvs.lineTo(xy.x, xy.y - height + this.xy[1]);
      this.cvs.stroke();
      this.cvs.fill();

      this.cvs.beginPath();
      this.cvs.moveTo(xy.x + this.xy[0], xy.y - height);
      this.cvs.lineTo(xy.x, xy.y - height - this.xy[1]);
      this.cvs.lineTo(xy.x - this.xy[0], xy.y - height);
      this.cvs.lineTo(xy.x, xy.y - height + this.xy[1]);
      this.cvs.stroke();
      this.cvs.fill();
    },
    //画圆柱体
    $$_drawCylinder(xy, height, color) {
      this.cvs.lineWidth = 0.6;
      //画圆形线，贝塞尔曲线来画
      this.cvs.beginPath();
      this.cvs.fillStyle = color[1]; 
      this.cvs.strokeStyle = color[0]; //"rgb(100, 100, 200)";
      this.cvs.moveTo(xy.x - this.xy[0], xy.y - this.xy[1]);
      const k = this.xy[1] / 0.75;
      // this.cvs.bezierCurveTo(xy.x - k, xy.y, xy.x - k, xy.y - this.xy[1] * 2, xy.x, xy.y - this.xy[1] * 2);
      // this.cvs.bezierCurveTo(xy.x + k, xy.y - this.xy[1] * 2, xy.x + k, xy.y, xy.x, xy.y);
      this.cvs.bezierCurveTo(xy.x - this.xy[0], xy.y - this.xy[1] - k, xy.x + this.xy[0], xy.y - this.xy[1] - k, xy.x + this.xy[0], xy.y - this.xy[1]);
      this.cvs.bezierCurveTo(xy.x + this.xy[0], xy.y - this.xy[1] + k, xy.x - this.xy[0], xy.y - this.xy[1] + k, xy.x - this.xy[0], xy.y - this.xy[1]);
      this.cvs.fill();
      this.cvs.stroke();

      //填充柱面
      this.cvs.beginPath();
      this.cvs.moveTo(xy.x - this.xy[0], xy.y - this.xy[1]);
      this.cvs.bezierCurveTo(xy.x - this.xy[0], xy.y - this.xy[1] - k, xy.x + this.xy[0], xy.y - this.xy[1] - k, xy.x + this.xy[0], xy.y - this.xy[1]);
      this.cvs.lineTo(xy.x + this.xy[0], xy.y - this.xy[1] - height);
      this.cvs.bezierCurveTo(xy.x + this.xy[0], xy.y - this.xy[1] - height + k, xy.x - this.xy[0], xy.y - this.xy[1] - height + k, xy.x - this.xy[0], xy.y - this.xy[1] - height);
      this.cvs.lineTo(xy.x - this.xy[0], xy.y - this.xy[1]);
      this.cvs.fill();
      //柱子画线
      this.cvs.beginPath();
      this.cvs.moveTo(xy.x - this.xy[0], xy.y - this.xy[1]);
      this.cvs.lineTo(xy.x - this.xy[0], xy.y - this.xy[1] - height);
      this.cvs.moveTo(xy.x + this.xy[0], xy.y - this.xy[1]);
      this.cvs.lineTo(xy.x + this.xy[0], xy.y - this.xy[1] - height);
      this.cvs.stroke();
      
      //填充顶面
      this.cvs.beginPath();
      this.cvs.fillStyle = color[1];
      this.cvs.moveTo(xy.x - this.xy[0], xy.y - height - this.xy[1]);
      this.cvs.bezierCurveTo(xy.x - this.xy[0], xy.y - this.xy[1] - height - k, xy.x + this.xy[0], xy.y - this.xy[1] - height - k, xy.x + this.xy[0], xy.y - this.xy[1] - height);
      this.cvs.bezierCurveTo(xy.x + this.xy[0], xy.y - this.xy[1] - height + k, xy.x - this.xy[0], xy.y - this.xy[1] - height + k, xy.x - this.xy[0], xy.y - this.xy[1] - height);
      this.cvs.stroke();
      this.cvs.fill();

      this.cvs.beginPath();
      const b = 0.7;
      const thisXy0 = this.xy[0] * b, thisXy1 = this.xy[1] * b;
      const k2 = thisXy1 / 0.75;
      this.cvs.moveTo(xy.x - thisXy0, xy.y - height - this.xy[1]);
      this.cvs.bezierCurveTo(xy.x - thisXy0, xy.y - this.xy[1] - height - k2, xy.x + thisXy0, xy.y - this.xy[1] - height - k2, xy.x + thisXy0, xy.y - this.xy[1] - height);
      this.cvs.bezierCurveTo(xy.x + thisXy0, xy.y - this.xy[1] - height + k2, xy.x - thisXy0, xy.y - this.xy[1] - height + k2, xy.x - thisXy0, xy.y - this.xy[1] - height);
      this.cvs.fillStyle = color[0];
      this.cvs.fill();
    },
    //此方法只适用于显示数字，没有样式
    $$_drawTopTipNumber(value, xy, height, color) {
      if (!this.showTopTipNumber) return;
      this.cvs.beginPath();
      if (this.topTipColor) {
        this.cvs.fillStyle = this.topTipColor;
      } else {
        this.cvs.fillStyle = color;
      }
      this.cvs.font = `${this.topTipFontSize} sans-serif`;
      this.cvs.textAlign = "center";
      this.cvs.textBaseline = "bottom";
      //写数字
      let t = 2;
      if (this.type === TypeConstant.cylinder) t = 6;
      if (this.isHorizontal) {
        this.cvs.save();
        this.cvs.textAlign = "left";
        this.cvs.textBaseline = "middle";
        this.cvs.translate(xy.x, xy.y - height - this.xy[1] - t);
        this.cvs.rotate(-90 * Math.PI / 180);
        this.cvs.fillText(value, 0 + t, 0);
        this.cvs.restore();
      } else {
        this.cvs.fillText(value, xy.x, xy.y - height - this.xy[1] - t);
      }
    }
  },
  beforeDestroyed() {
    
  },
  props: {
    /**
     * {
     *  data: ['2017', '2018', '2019', '2020'],
     *  color: 'gray'
     * }
     */
    xAxis: {
      type: Object,
      required: true
    },
    /**
     * {
     *  data: ['0', '200', '400', '600'],
     *  color: 'gray'
     * }
     */
    yAxis: {
      type: Object,
      required: true
    },
    /**
     * [{
     *  data: [320, 300, 400, 550],
     *  color: ['red', 'green']   //深色，浅色
     * }, ...]
     */
    series: {   //目前定死 数组大小 = 1/2
      type: Array,
      required: true
    },
    showTopTipNumber: Boolean,//是否显示柱状图上边的数字
    topTipColor: String,    //showTopTipNumber -> true 的时候才使用
    topTipFontSize: {    //showTopTipNumber -> true 的时候才使用
      type: String,
      default: "12px"
    },
    showLegend: {   //是否显示legend，默认不显示
      type: Boolean,
      default: false
    },
    tipTrigger: {   //目前只支持直接显示
      type: String,
      default: TipTriggerConstant.show
    },
    showBoxTip: Boolean,    //默认不显示
    padding: {
      type: Array,
      default: _ => [50, 45]  //[上下, 左]
    },
    fontSize: {
      type: String,
      default: "16px"
    },
    xy: {  //距x轴最近的那个中心点，距离左边点的x长度y高度
      type: Array,
      default: _ => [10, 6] //比如[8, 6]->则斜边为 10  （勾股定理）
    },
    type: {   //显示的类型，默认长方体
      type: String,
      default: TypeConstant.cuboid
    },
    direction: {
      type: String,
      default: DirectionConstant.vertical
    }
  }
}
</script>

<style lang="less" scoped>
@import "./style.less";
</style>